#include <QDebug>
#include "indicators.h"


const int iconsize = 48;

QIndicator::QIndicator(QWidget *parent, QIcon icon) : QLabel(parent), m_icon(icon), m_target(parent)
{
    updatePixmap(iconsize);
    setHovered(false);
}

void QIndicator::resizeEvent(QResizeEvent *event)
{
    QLabel::resizeEvent(event);
}

void QIndicator::setHovered(bool value)
{
    m_hovered = value;
    updatePixmap(m_iconsize);


TODO:;//use other size hinting technique
//    if (value){
//    this->setBackgroundRole(QPalette::Highlight);
//    this->setAutoFillBackground(true);
//    }
//    else {
//        this->setBackgroundRole(QPalette::Base);
//    }
//    qDebug()<< pixmap.width();
    this->setMargin(0);
    this->setFrameStyle(0);
}

void QIndicator::updatePixmap(int size){
    m_iconsize = size;
    QPixmap pixmap;
    if (m_hovered){
        QList<QSize> sizes = m_icon.availableSizes(QIcon::Selected);
        qSort(sizes.begin(),sizes.end(),[](const QSize & first, const QSize & second){
              return first.width() < second.width();
        });
        pixmap = m_icon.pixmap(sizes.last(),QIcon::Selected).scaled(QSize(m_iconsize,m_iconsize),Qt::KeepAspectRatio,Qt::SmoothTransformation);
    }
    else {
//        pixmap = m_icon.pixmap(size,QIcon::Normal);
        QList<QSize> sizes = m_icon.availableSizes(QIcon::Normal);
        qSort(sizes.begin(),sizes.end(),[](const QSize & first, const QSize & second){
              return first.width() < second.width();
        });
        pixmap = m_icon.pixmap(sizes.last(),QIcon::Normal).scaled(QSize(m_iconsize,m_iconsize),Qt::KeepAspectRatio,Qt::SmoothTransformation);
    }
    this->setPixmap(pixmap);
    this->setFixedSize(pixmap.width(),pixmap.height());
}

void QIndicator::resizeIndicator(const QRect &rec)
{
    this->setGeometry(rec);
    updatePixmap(rec.width());

}



QLeftIndicator::QLeftIndicator(QWidget *parent):QIndicator(parent, QIcon::fromTheme(QString("go-previous")))
{
}

QBottomIndicator::QBottomIndicator(QWidget *parent):QIndicator(parent, QIcon::fromTheme(QString("go-down")))
{
}

QTopIndicator::QTopIndicator(QWidget *parent):QIndicator(parent, QIcon::fromTheme(QString("go-up")))
{
}

QRightIndicator::QRightIndicator(QWidget *parent):QIndicator(parent, QIcon::fromTheme(QString("go-next")))
{
}

QTabbedIndicator::QTabbedIndicator(QWidget *parent):QIndicator(parent, QIcon::fromTheme(QString("format-justify-center")))
{
}

IndividualIndicators::IndividualIndicators()
{
    m_bottom_indicator = new QBottomIndicator();
    m_top_indicator = new QTopIndicator();
    m_left_indicator = new QLeftIndicator();
    m_right_indicator = new QRightIndicator();
    m_tabbed_indicator = new QTabbedIndicator();
}

IndividualIndicators::~IndividualIndicators(){
    mapToIndicators([](QIndicator* ind){delete ind;});
}

auto sortx = [](const QRect& rec1,const QRect& rec2)-> bool {
        if (rec1.x()< rec2.x()){
            return true;
        }
        else if (rec1.x() == rec2.x()){
            return rec1.right() < rec2.right();
        }
        return false;
    };

auto sorty = [](const QRect& rec1,const QRect& rec2)-> bool {
    if (rec1.y()< rec2.y()){
        return true;
    }
    else if (rec1.y() == rec2.y()){
        return rec1.bottom() < rec2.bottom();
    }
    return false;
};

void removeDuplicates(QList<QRect> & activerects){
    //                qSort(activerects.begin(),activerects.end(),sortx);
    qSort(activerects.begin(),activerects.end(),sorty);

    for (int i=0;i<activerects.size()-1;i++){
        if (activerects[i].y() == activerects[i+1].y()){
            if (activerects[i].bottom() == activerects[i+1].bottom()){
                //remove the smaller one
                if (activerects[i].x()<activerects[i+1].x()){

                    activerects.removeAt(i+1);
                }else {
                    activerects.removeAt(i);
                }
                i--;

            }
        }
    }
}



QList<QRect> maximumRects(QRect base, QList<QRect> obstacles) {
    QList<QRect> activerects;
    activerects.append(base);
    QList<QRect> sortedobstacles(obstacles);
    QList<QRect> terminatedrects;

    qSort(sortedobstacles.begin(),sortedobstacles.end(),sortx);

    for (const QRect& obstacle:sortedobstacles) {

        for (int i=0; i<activerects.size(); i++) {
            const QRect & rec = activerects[i];
            if (rec.intersects(obstacle)) {
                // each intersection spawns 0 to 3 new rects
                // and terminates 0 or 1
                if (rec.left() < obstacle.left()) {
                    //rec has to be finalized
                    QRect terminated(rec);
                    terminated.setRight(obstacle.x() - 1);                    
                    terminatedrects.append(terminated);
                }
                if (rec.top() < obstacle.top()){
                    //spawn new rectangle above the obstacle
                    QRect spawn(rec);
                    spawn.setBottom(obstacle.top()-1);
                    activerects.append(spawn);
                }
                if (rec.bottom() > obstacle.bottom()){
                    //spawn new rectangle below the obstacle
                    QRect spawn(rec);
                    spawn.setTop(obstacle.bottom() + 1);
                    activerects.append(spawn);
                }
                if (rec.right() > obstacle.right()){
                    //spawn new rectangle on the right of the obstacle
                    QRect spawn(rec);
                    spawn.setLeft(obstacle.right()+1);;
                    activerects.append(spawn);
                }
                activerects.removeAt(i);
                i--;
            }
        }
        removeDuplicates(activerects);
    }
    terminatedrects.append(activerects);
    return terminatedrects;
}

struct RectAndCenter {
    int leftspace;
    int rightspace;
    int topspace;
    int bottomspace;
};

QRect fit(const QRect & fit_to, RectAndCenter rc){
    QRect ret(fit_to);
    ret.adjust(rc.leftspace,rc.topspace,-(rc.rightspace-1),-(rc.bottomspace-1));

    return ret;
}


IndividualIndicators::CenterAndSize IndividualIndicators::optimizedCenter(QList<QRect> maxRects, const QPoint &preferred_center, DockOption dockoptions){
    int topheight = dockoptions & DockOption::Top ? 1 : 0;
    int bottomheight = dockoptions & DockOption::Bottom ? 1 : 0;
    int leftwidth = dockoptions & DockOption::Left ? 1 : 0;
    int rightwidth = dockoptions & DockOption::Right ? 1 : 0;
    int fixedchange = 0;
    QList<CenterAndSize> solutions;
    for (int lastchange = iconsize;lastchange; lastchange = lastchange <= 2?1:lastchange/ 2 + 1){
        int ind_size = lastchange + fixedchange;
        if (ind_size > iconsize)
            break;

        RectAndCenter rc1;
        rc1.leftspace = ind_size/2;
        rc1.rightspace = ind_size - ind_size/2;
        rc1.topspace = topheight * ind_size + ind_size/2;
        rc1.bottomspace = bottomheight * ind_size + ind_size - ind_size/2;
        RectAndCenter rc2;
        rc2.leftspace = leftwidth * ind_size + ind_size/2;
        rc2.rightspace = rightwidth * ind_size + ind_size - ind_size/2;
        rc2.topspace = ind_size/2;
        rc2.bottomspace = ind_size - ind_size/2;
        //fit the rectangles
        QList<QRect> fittings1;
        QList<QRect> fittings2;
        for (const QRect & rec : maxRects){
            auto f = fit(rec,rc1);
            if (f.isValid())
                fittings1.append(f);
            f = fit(rec,rc2);
            if (f.isValid())
                fittings2.append(f);
        }
        QList<QRect> intersections;
        for (auto fit1 : fittings1){
            for (auto fit2 : fittings2){
                if (fit1.intersects(fit2))
                    intersections.append(fit1 & fit2);
            }
        }
        if (!intersections.empty()){
            for (auto intersection :intersections){
                CenterAndSize solution;
                solution.iconsize = ind_size;
                if (intersection.right() < preferred_center.x()){
                    //point is on the right
                    solution.center.setX(intersection.right());
                }
                else if (intersection.left() <= preferred_center.x()){
                    //point is inside the rectangle
                    solution.center.setX(preferred_center.x());
                }
                else {
                    //point is on the left
                    solution.center.setX(intersection.left());
                }

                if (intersection.bottom() < preferred_center.y()){
                    //point is on the bottom
                    solution.center.setY(intersection.bottom());
                }
                else if (intersection.top() <= preferred_center.y()){
                    //point is inside the rectangle
                    solution.center.setY(preferred_center.y());
                }
                else {
                    //point is on the top
                    solution.center.setY(intersection.top());
                }

                solutions.append(solution);
            }
            fixedchange += lastchange;
        }
        else {
            if (lastchange == 1)
                break;
        }
    }
    qSort(solutions.begin(),solutions.end(),[&preferred_center](const CenterAndSize& first, const CenterAndSize& second) -> bool{
          if (first.iconsize > second.iconsize){
            return true;
            }
          else if (first.iconsize == second.iconsize){
    int firstdistance = abs(preferred_center.x() - first.center.x())
            + abs(preferred_center.y() - first.center.y());
    int seconddistance = abs(preferred_center.x() - second.center.x())
            + abs(preferred_center.y() - second.center.y());

                return firstdistance < seconddistance;
            }
          return false;
    });
    if (solutions.empty())
        return CenterAndSize({0,QPoint()});
    return solutions.first();
}


void IndividualIndicators::put(QWidget *target, QList<QRect> obstacles, DockOption dockoptions)
{
    m_target = target;
    mapToIndicators([target](QIndicator* ind){ind->setTarget(target);});
    //TODO: position && evasion
    QRect target_rec = target->rect();
    QPoint point_center = target->mapToGlobal(target_rec.center());
    QRect rec_center;

    QPoint tmp = point_center;
    tmp -= QPoint(m_tabbed_indicator->width()/2,
                              m_tabbed_indicator->height()/2);
    rec_center.setTopLeft(tmp);
    tmp += m_tabbed_indicator->rect().bottomRight();
    rec_center.setBottomRight(tmp);
    //TODO: evasion
    //...
    
    QRect targetrect = target->rect();
    targetrect.moveTopLeft(target->mapToGlobal(targetrect.topLeft()));
    auto rects = maximumRects(targetrect, obstacles);

    //optimize
    CenterAndSize optimized = optimizedCenter(rects, targetrect.center(),dockoptions);

    QSize size(optimized.iconsize,optimized.iconsize);
    rec_center.setSize(size);

    QWidget* parent;
    while (parent = target->parentWidget()){
        target = parent;
    }

    rec_center.moveCenter(target->mapFromGlobal(optimized.center));
//    rec_center.moveTopLeft(target->mapFromGlobal(rec_center.topLeft()));
    QPoint relative_center = target->mapFromGlobal(optimized.center);


int crap = 0;
    mapToIndicators(
        [target, &rec_center, &size](QIndicator* ind){
            ind->setParent(target);
            switch (ind->dockoption()){
                case DockOption::Left:
                    {
                        QRect rec = rec_center;
                        rec.moveLeft(rec.left() - size.width());
                        rec.setSize(size);
                        ind->resizeIndicator(rec);
                    }
                    break;
                case DockOption::Right:
                    {
                        QRect rec = rec_center;
                        rec.moveRight(rec.right() + size.width());
                        rec.setSize(size);
                        ind->resizeIndicator(rec);
                    }
                    break;
                case DockOption::Top:
                    {
                        QRect rec = rec_center;
                        rec.moveTop(rec.top() - size.height());
                        rec.setSize(size);
                        ind->resizeIndicator(rec);
                    }
                    break;
                case DockOption::Bottom:
                    {
                        QRect rec = rec_center;
                        rec.moveBottom(rec.bottom() + size.height());
                        rec.setSize(size);
                        ind->resizeIndicator(rec);
                    }
                    break;
                case DockOption::Tabbed:
                    ind->resizeIndicator(rec_center);
                    break;
           }
        }
    );
    mapToIndicators(
        [target, dockoptions](QIndicator* ind){
            if (dockoptions & ind->dockoption()){
                ind->show();
            }
            else {
                ind->hide();
            }
        }
    );
}

QIndicator *IndividualIndicators::hoveredIndicator(const QPoint &point)
{
    QIndicator* ret = 0;
    mapToIndicators(
        [&ret,point](QIndicator* ind){
                if (!ind->isVisible())
                return 0;
            QRect realrect = ind->rect();
            realrect.moveTopLeft(ind->mapToGlobal(realrect.topLeft()));
            if (realrect.contains(point)){
                 ret = ind;
            }
        }
    );
return ret;
}

QWidget *IndividualIndicators::currentTarget() const
{
    return m_target;
}

void IndividualIndicators::hide(){
    mapToIndicators([](QIndicator* ind){
                    ind->setParent(0);
    });
}

void IndividualIndicators::mapToIndicators(std::function<void(QIndicator*)> foo)
{
    foo(m_bottom_indicator);
    foo(m_top_indicator);
    foo(m_left_indicator);
    foo(m_right_indicator);
    foo(m_tabbed_indicator);
}

















